﻿using System;
using System.IO;
using System.Runtime.InteropServices;
using ICSharpCode.SharpZipLib.Zip;

namespace Player.Core
{
    public class MemoryZip
    {
        private static int m_level = 9;

        public static int Level
        {
            get { return m_level; }
            set { m_level = value; }
        }

        public static byte[] Compress(byte[] data)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (ZipOutputStream s = new ZipOutputStream(ms))
                {
                    ZipEntry en = new ZipEntry("x");
                    en.DateTime = DateTime.Now;
                    s.SetLevel(m_level);
                    s.PutNextEntry(en);
                    s.Write(data, 0, data.Length);
                    s.Finish();
                    s.Close();
                }
                return ms.ToArray();
            }
        }

        public static byte[] Decompress(byte[] data)
        {
            byte[] res = null;
            using (MemoryStream ms = new MemoryStream(data))
            {
                using (ZipInputStream s = new ZipInputStream(ms))
                {
                    ZipEntry en = s.GetNextEntry();
                    res = new byte[en.Size];
                    s.Read(res, 0, (int)en.Size);
                }
            }
            return res;
        }

        public static int DecompressedSize(byte[] data)
        {
            using (MemoryStream ms = new MemoryStream(data))
            {
                using (ZipInputStream s = new ZipInputStream(ms))
                {
                    ZipEntry en = s.GetNextEntry();
                    return (int)en.Size;
                }
            }
        }

        public static void Decompress(byte[] data, byte[] buffer)
        {
            using (MemoryStream ms = new MemoryStream(data))
            {
                using (ZipInputStream s = new ZipInputStream(ms))
                {
                    ZipEntry en = s.GetNextEntry();
                    if (buffer.Length < en.Size)
                        throw new ArgumentException("Provided buffer is not large enough to decompress into", "buffer");

                    s.Read(buffer, 0, (int)en.Size);
                }
            }
        }
    }

    public class QuickLZ
    {
        private static bool m_is64bit = Marshal.SizeOf(typeof(IntPtr)) == 8;

        private interface LZCompressor
        {
            byte[] Compress(byte[] data);
            byte[] Decompress(byte[] data);
            /** Not actually faster, leave out for now
            byte[] FastCompress(byte[] data);            
            uint BufferSize(byte[] data);
             */
        }

        #region x86 P/Invokes
        private class QLZ32 : LZCompressor
        {
            private byte[] scratch_compress = null;
            private byte[] scratch_decompress = null;

            [DllImport("quicklz32.dll")]
            private static extern IntPtr qlz_compress(byte[] source, byte[] dest, IntPtr size, byte[] scratch);
            [DllImport("quicklz32.dll")]
            private static extern IntPtr qlz_decompress(byte[] source, byte[] dest, byte[] scratch);
            [DllImport("quicklz32.dll")]
            private static extern IntPtr qlz_scratch_compress();
            [DllImport("quicklz32.dll")]
            private static extern IntPtr qlz_scratch_decompress();
            [DllImport("quicklz32.dll")]
            private static extern IntPtr qlz_size_compressed(byte[] source);
            [DllImport("quicklz32.dll")]
            private static extern IntPtr qlz_size_decompressed(byte[] source);

            public QLZ32()
            {
                scratch_compress = new byte[(int)qlz_scratch_compress()];
                scratch_decompress = new byte[(int)qlz_scratch_decompress()];
            }

            public byte[] Compress(byte[] data)
            {
                byte[] compressed = new byte[data.Length + 400];
                uint len = (uint)qlz_compress(data, compressed, (IntPtr)data.Length, scratch_compress);
                byte[] ret = new byte[len];
                Array.Copy(compressed, ret, len);
                return ret;
            }

            /*
             * Not actually that much faster, avoid the confusion and leave this out for now
            public byte[] FastCompress(byte[] data)
            {
                byte[] compressed = new byte[data.Length + 400];
                qlz_compress(data, compressed, (IntPtr)data.Length, scratch_compress);
                return compressed;
            }

            public uint BufferSize(byte[] data)
            {
                return (uint)qlz_size_compressed(data);
            }
            */

            public byte[] Decompress(byte[] data)
            {
                int len = (int)qlz_size_decompressed(data);
                byte[] ret = new byte[len];
                qlz_decompress(data, ret, scratch_decompress);
                return ret;
            }
        }
        #endregion
        #region x64 P/Invokes
        private class QLZ64 : LZCompressor
        {
            private byte[] scratch_compress = null;
            private byte[] scratch_decompress = null;

            [DllImport("quicklz64.dll")]
            private static extern IntPtr qlz_compress(byte[] source, byte[] dest, IntPtr size, byte[] scratch);
            [DllImport("quicklz64.dll")]
            private static extern IntPtr qlz_decompress(byte[] source, byte[] dest, byte[] scratch);
            [DllImport("quicklz64.dll")]
            private static extern IntPtr qlz_scratch_compress();
            [DllImport("quicklz64.dll")]
            private static extern IntPtr qlz_scratch_decompress();
            [DllImport("quicklz64.dll")]
            private static extern IntPtr qlz_size_compressed(byte[] source);
            [DllImport("quicklz64.dll")]
            private static extern IntPtr qlz_size_decompressed(byte[] source);

            public QLZ64()
            {
                scratch_compress = new byte[(int)qlz_scratch_compress()];
                scratch_decompress = new byte[(int)qlz_scratch_decompress()];
            }

            public byte[] Compress(byte[] data)
            {
                byte[] compressed = new byte[data.Length + 400];
                uint len = (uint)qlz_compress(data, compressed, (IntPtr)data.Length, scratch_compress);
                byte[] ret = new byte[len];
                Array.Copy(compressed, ret, len);
                return ret;
            }

            /*
             * Not actually that much faster, avoid the confusion and leave this out for now
            public byte[] FastCompress(byte[] data)
            {
                byte[] compressed = new byte[data.Length + 400];
                qlz_compress(data, compressed, (IntPtr)data.Length, scratch_compress);
                return compressed;
            }

            public uint BufferSize(byte[] data)
            {
                return (uint)qlz_size_compressed(data);
            }
            */

            public byte[] Decompress(byte[] data)
            {
                int len = (int)qlz_size_decompressed(data);
                byte[] ret = new byte[len];
                qlz_decompress(data, ret, scratch_decompress);
                return ret;
            }
        }
        #endregion

        private LZCompressor m_compressor = null;

        public QuickLZ()
        {
            if (m_is64bit)
                m_compressor = (LZCompressor)new QLZ64();
            else
                m_compressor = (LZCompressor)new QLZ32();
        }

        public byte[] Compress(byte[] data)
        {
            return m_compressor.Compress(data);
        }

        public byte[] Decompress(byte[] data)
        {
            return m_compressor.Decompress(data);
        }
    }
}
